今日內容:public, private, protective、什麼是套件(package)、反向域名命名法 (Reverse Domain Name)、package的使用方式
public, private 和 protected 是存取修飾子 (Access Modifiers),他們用於控制class、attributes、methods或constructor可以被誰存取,是用來定義 可見性 (visibility)
的規則。
public (公開)
public 是最寬鬆的存取層級。public的成員可以在任何地方被存取,且public class也可以有private的內容(class本身可以在任何地方被存取、可是內部資料只能在class內部存取,也就是上方使用getter&setter的原因)。
使用時機:當你希望一個class或method或attribute對所有其他的class都可見時
例如,一個名為 Utility的public class,或一個公開的method 例如 public void countSomething()
例子:
public class Car:Car 可以在任何套件 (package) 中被使用。
public String color:Car object的 color 屬性可以從任何地方直接存取與修改,例如 myCar.color = "Red";。
private (私有)
private 是最嚴格的存取層級。private的成員只能在它所屬的class內部被存取,用以確保資料安全性。
使用時機:當你希望封裝 (encapsulation) class的內部實作細節,避免外部的直接存取和修改
這是物件導向設計(OOP)的最佳實踐與目的,用來保護資料。
例子:
private int speed:Car object的 speed 屬性只能在 Car class內部被使用。外部無法直接存取,例如 myCar.speed = 10 會報錯。
通常會提供 public 的 getter 和 setter methods來間接存取這些 private 屬性,例如 public int getSpeed() 和 public void setSpeed(int speed)。這能讓你對資料的存取進行控制和驗證。
protected (受保護)
protected 是介於 public 和 private 之間的存取層級。protected的成員只能在以下兩個地方被存取:
使用時機: 當你希望一個attribute或methods對同一個繼承關係的class開放,但又對外部封閉時。這讓subclass可以繼承並使用parent class的內部細節,同時又保護了這些細節不被不相關的類別隨意存取。
例子:
protected int wheelCount:如果 Car 有一個 protected 的 wheelCount,在同一個套件裡的 Truck 也可以存取它。而一個不同套件的subclass,例如 SportsCar extends Car,也可以存取這個屬性。
預設存取層級
public class:如果一個class是 public 的,它的檔案名稱必須和類別名稱相同(例如:public class MyClass 這個class 必須存在於 MyClass.java 檔案中)。這是硬性規定,一個 .java file 中最多只能有一個 public class。
預設 (無修飾子) class:如果一個class沒有任何存取修飾子,它就是預設的套件私有 (Package-Private)。這表示這個類別只能在同一個套件 (package) 內的類別中被存取,無法被其他套件的類別使用。宣告成預設的class通常是用來作為輔助類別(Helper class)
想像一下你的程式碼就是一座巨大的公寓大樓。這座大樓裡住著各種各樣的人,也就是你的class和interface。
而package就是這座「公寓大樓」的「樓層」
每一個樓層都有一個自己的名稱,例如「com.example.game」或「com.example.util」。
每個class就是一個住戶,屬於同一個樓層的住戶通常是鄰居,彼此之間會有密切關係,功能上也經常互相依賴。
你不能把一個住戶同時放在兩個不同的樓層。同樣地,一個 Java 檔案 (.java file)也只能屬於一個 package。
每個住戶都有一個獨特的名稱,例如 Player 或 Monster。
即使其他大樓(其他專案)也有一個住戶叫做 Player,只要他們在不同的大樓裡,就不會搞混。同樣地,com.example.game.Player 和 com.othercompany.engine.Player 是兩個完全不同的類別。
有了 package (樓層) 的概念,我們就能解決兩個重要的問題:
管理與組織
如果沒有樓層,所有住戶都擠在一個地方,找人就會非常困難。
package 讓你可以把功能相似的class放在一起。例如,所有遊戲角色的類別都放在 game 樓層,所有工具程式都放在 util 樓層,使得程式碼結構清晰,更容易找到需要的東西。
避免名稱衝突
世界上有很多人都叫「小明」。但只要加上他們住的地址,就不會搞錯。例如,「台北市忠孝東路三段的小明」和「台中市文心路二段的小明」是不同的兩個人。
同樣地,package 提供了這樣的命名屬性。即使你的class名稱和其他的重複了,只要它們在不同的 package 中,就不會有問題。
public (公開):
這就像一個住在樓層頂樓的住戶,或者想成社區的健身房,他的大門對所有人開放,任何人都可以隨時去拜訪他。
private (私有):
這就像一個普通住戶的房間,只有他自己(同個class)才能進去。其他人(包括同個樓層的鄰居)都不能進入。
protected (受保護):
這就像一個住戶的公寓大門只對同樓層的鄰居,以及繼承他的subclass開放。其他人即使知道他住在哪層樓,也進不去他的公寓。
package-private (預設):
這就像一個住戶的公寓大門只對同樓層的鄰居開放。只有住在同一層樓的人可以自由地進出,而住在其他樓層的人則不能。
由於我目前還沒學會製作專案,因此我src資料夾下的 Main.java 和 Animal.java 這種檔案,其實是在沒有明確指定套件的情況下,它們都被放在一個預設的、無名的套件 (default package) 裡。
這些資料夾名稱只是為了標識你的程式碼。
以上述舉例中的 com.example.game 進行解釋
com:代表 company (公司),這是標準約定的第一層資料夾。
example:代表你的公司名稱或專案名稱,例如 google、apple 或 myproject。
game:代表你這個專案或功能模組的名稱,例如 database、ui 或 network。
假設你正在開發一個遊戲,而你公司的網域名稱(domain name)是 example.com。根據 Java 的慣例,你的程式碼套件名稱就會以 com.example (reversed) 開頭。
當你的專案成長,你會有越來越多的程式碼檔案。這時,你可以把不同功能的類別放在不同的子套件中:
src/com/example/game/characters/Player.java:存放遊戲角色的類別。
src/com/example/game/utils/MathUtils.java:存放遊戲中用到的數學工具類別。
src/com/example/game/Main.java:存放遊戲的啟動程式。
只要掌握兩個步驟:宣告 (declare) 和匯入 (import)。
當你開始寫一個新的 Java 類別時,你需要在檔案的最頂部宣告它屬於哪一個套件。
舉例來說,我們來建立一個專案,包含兩個不同的套件:utils (工具) 和 main (主程式)。
創建資料夾結構
在 src 資料夾下,創建以下目錄結構:
src/
└── com/
└── myproject/
├── utils/
│ └── MathUtils.java // 存放數學工具
└── main/
└── Main.java // 存放主程式
宣告 utils 這個 package
在 src/com/myproject/utils/MathUtils.java 檔案中,在class宣告前加上一行程式碼:
package com.myproject.utils; // 宣告此類別屬於 com.myproject.utils package
public class MathUtils {
public static int add(int a, int b) {
return a + b;
}
}
宣告 main 的 package
同樣地,在 src/com/myproject/main/Main.java 檔案中,宣告它屬於 com.myproject.main 套件:
package com.myproject.main; // 宣告此類別屬於 com.myproject.main 套件
// ... 後面再介紹如何使用 MathUtils 這個 class
當你需要在一個class中使用其他package中的class時,你必須先匯入 (import) 它。
回到我們的 Main.java 檔案,現在我們想使用 MathUtils 這個class來執行加法運算。
在 Main.java 檔案中,在 package 宣告之後、宣告Main class之前,加入 import 語句:
package com.myproject.main;
// 匯入 com.myproject.utils package 中的 MathUtils class
import com.myproject.utils.MathUtils;
public class Main {
public static void main(String[] args) {
// 現在就可以直接使用 MathUtils 這個class的內容了
int sum = MathUtils.add(5, 3);
System.out.println("The sum is: " + sum);
}
}
宣告 (Declaration):用 package 關鍵字來告訴 Java ,這個class的「地址」。這是所有非預設套件 (default package) 的class都必須做的第一件事。
匯入 (Import):用 import 關鍵字來告訴 Java 編譯器你要使用哪些其他package的class。
撇步
今天是為自己解決Java問題的一天,在昨天把學到的知識整理過後,發現自己不理解Access Modifier的詳細運作模式,詢問Gemini後又問到有關Package的問題,所以今天一次把這些問題處理掉。
今天也是快樂學習的一天,明天繼續!